/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// in_win.c -- windows 95 mouse and joystick code

#include "quakedef.h"

HCURSOR hArrowCursor = NULL;
extern cvar_t *vid_fullscreen;

/*
===================================================================

KEY MAPPING

moved from gl_vidnt.c
shiftscantokey was unused

===================================================================
*/
byte scantokey[128] = {
	// scancode to quake key table
	// 0     1     2     3     4     5     6     7     8     9     a     b     c     d     e     f
	0x00, 0x1b, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2d, 0x3d, 0x7f, 0x09,		// 0x0
	0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6f, 0x70, 0x5b, 0x5d, 0x0d, 0x85, 0x61, 0x73,		// 0x1
	0x64, 0x66, 0x67, 0x68, 0x6a, 0x6b, 0x6c, 0x3b, 0x27, 0x60, 0x86, 0x5c, 0x7a, 0x78, 0x63, 0x76,		// 0x2
	0x62, 0x6e, 0x6d, 0x2c, 0x2e, 0x2f, 0x86, 0x2a, 0x84, 0x20, 0x99, 0x87, 0x88, 0x89, 0x8a, 0x8b,		// 0x3
	0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0xff, 0x00, 0x97, 0x80, 0x96, 0x2d, 0x82, 0x35, 0x83, 0x2b, 0x98,		// 0x4
	0x81, 0x95, 0x93, 0x94, 0x00, 0x00, 0x00, 0x91, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,		// 0x5
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,		// 0x6
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00		// 0x7
};


int IN_MapKey (int key)
{
	// extract the portion of the scancode that identifies the key
	key = (key >> 16) & 255;

	if (key > 127)
		return 0;

	if (scantokey[key] == 0)
		Con_Printf (PRINT_DEVELOPER, "key 0x%02x has no translation\n", key);

	return scantokey[key];
}


// needed so that we know we're in the keybindings menu and we need to activate the mouse
extern int bind_grab;

// mouse variables
cvar_t	*freelook;
BOOL	mouseactive;
RECT	in_WindowRect;

void Force_CenterView_f (void)
{
	cl.viewangles[PITCH] = 0;
}


void IN_ClearMouseButtonStates (void)
{
	// clear the mouse button states
	Key_ClearButton (K_MOUSE1);
	Key_ClearButton (K_MOUSE2);
	Key_ClearButton (K_MOUSE3);
	Key_ClearButton (K_MOUSE4);
	Key_ClearButton (K_MOUSE5);
}


void IN_ClearRawInputBuffer (void)
{
	Sys_SendKeyEvents (TRUE);

	int mark = qScratch->GetLowMark ();
	unsigned int cbSize = 0;

	// figure out how much there is to read
	if (GetRawInputBuffer (NULL, &cbSize, sizeof (RAWINPUTHEADER)) != 0) return;

	// nothing to read
	if (cbSize == 0) return;

	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645595%28v=vs.85%29.aspx
	// To get the correct size of the raw input buffer, do not use *pcbSize, use *pcbSize * 8 instead.
	cbSize *= 8;

	// and now we can allocate our buffer to read from
	RAWINPUT *pRawInput = (RAWINPUT *) qScratch->FastAlloc (cbSize);

	// and read them all
	for (;;)
	{
		// don't modify the original cbSize
		unsigned int cbSizeT = cbSize;
		unsigned int nInput = GetRawInputBuffer (pRawInput, &cbSizeT, sizeof (RAWINPUTHEADER));

		// nothing to read (an error just reads nothing)
		// (errors should only happen if cbSize is 0, which can't happen cos we return if so)
		if ((int) nInput <= 0) break;

		// this buffer is for passing to DefRawInputProc
		RAWINPUT **paRawInput = (RAWINPUT **) qScratch->FastAlloc (sizeof (RAWINPUT *) * nInput);
		RAWINPUT *raw = pRawInput;

		// and here we read the blocks in the current buffer
		for (int i = 0; i < nInput; i++, raw = NEXTRAWINPUTBLOCK (raw))
		{
			// store out the current block for passing to DefRawInputProc
			paRawInput[i] = raw;
		}

		// clean the buffer and remove messages from the system
		DefRawInputProc (paRawInput, nInput, sizeof (RAWINPUTHEADER));
	}

	// and release the scratch memory used for our buffers
	qScratch->FreeToLowMark (mark);
}


void IN_UpdateCursorClip (void)
{
	// constrain cursor movement to the window rect
	if (!mouseactive || vid.Minimized || !vid.ActiveApp)
		ClipCursor (NULL);
	else if (!vid_fullscreen->boolean)
	{
		// theoretically we only need to do this if we've got a WM_SIZE or WM_MOVE message, but this is
		// lightweight and absolutely ensures that everything is kept consistent so we do it always
		RECT WindowRect;

		GetClientRect (vid.Window, &WindowRect);

		ClientToScreen (vid.Window, (POINT *) &WindowRect.left);
		ClientToScreen (vid.Window, (POINT *) &WindowRect.right);

		// keep the cursor away from the edges
		WindowRect.left += 10;
		WindowRect.top += 10;
		WindowRect.right -= 10;
		WindowRect.bottom -= 10;

		ClipCursor (&WindowRect);
	}
	else ClipCursor (NULL);
}


void IN_CaptureMouse (BOOL forcecapture = FALSE)
{
	if (!mouseactive || forcecapture)
	{
		SetCursor (NULL);

		RAWINPUTDEVICE ri_Mouse = {1, 2, RIDEV_NOLEGACY, vid.Window};

		RegisterRawInputDevices (&ri_Mouse, 1, sizeof (RAWINPUTDEVICE));

		IN_UpdateCursorClip ();
		IN_ClearMouseButtonStates ();
		IN_ClearRawInputBuffer ();
		mouseactive = TRUE;
	}
}


void IN_ReleaseMouse (BOOL forcerelease = FALSE)
{
	if (mouseactive || forcerelease)
	{
		IN_ClearRawInputBuffer ();
		SetCursor (hArrowCursor);

		RAWINPUTDEVICE ri_Mouse = {1, 2, RIDEV_REMOVE, NULL};

		RegisterRawInputDevices (&ri_Mouse, 1, sizeof (RAWINPUTDEVICE));

		IN_UpdateCursorClip ();
		IN_ClearMouseButtonStates ();
		mouseactive = FALSE;
	}
}


void IN_SetMouseCapture (void)
{
	if (vid.Minimized || !vid.ActiveApp)
		IN_ReleaseMouse ();
	else if (bind_grab || key_dest == key_game)
		IN_CaptureMouse ();
	else if (!vid_fullscreen->boolean)
		IN_ReleaseMouse ();
	else IN_CaptureMouse ();
}


void IN_ReadRawInput (void)
{
	// don't read it if the mouse is not currently active
	if (!mouseactive) return;

	int mark = qScratch->GetLowMark ();
	unsigned int cbSize = 0;

	// figure out how much there is to read
	if (GetRawInputBuffer (NULL, &cbSize, sizeof (RAWINPUTHEADER)) != 0) return;

	// nothing to read
	if (cbSize == 0) return;

	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645595%28v=vs.85%29.aspx
	// To get the correct size of the raw input buffer, do not use *pcbSize, use *pcbSize * 8 instead.
	cbSize *= 8;

	// and now we can allocate our buffer to read from
	RAWINPUT *pRawInput = (RAWINPUT *) qScratch->FastAlloc (cbSize);

	// initial mouse movements
	int mx = 0;
	int my = 0;

	// and read them all
	for (;;)
	{
		// don't modify the original cbSize
		unsigned int cbSizeT = cbSize;
		unsigned int nInput = GetRawInputBuffer (pRawInput, &cbSizeT, sizeof (RAWINPUTHEADER));

		// nothing to read (an error just reads nothing)
		// (errors should only happen if cbSize is 0, which can't happen cos we return if so)
		if ((int) nInput <= 0) break;

		// this buffer is for passing to DefRawInputProc
		RAWINPUT **paRawInput = (RAWINPUT **) qScratch->FastAlloc (sizeof (RAWINPUT *) * nInput);
		RAWINPUT *raw = pRawInput;

		// and here we read the blocks in the current buffer
		for (int i = 0; i < nInput; i++, raw = NEXTRAWINPUTBLOCK (raw))
		{
			// store out the current block for passing to DefRawInputProc
			paRawInput[i] = raw;

			// check it for the type of input we want
			if (raw->header.dwType == RIM_TYPEMOUSE)
			{
				// here we need to copy off the buffer and re-align it if running on WOW64
				RAWMOUSE *rm = NULL;

				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645595%28v=vs.85%29.aspx - [FieldOffset(16+8)]
				if (sys_isWOW64)
					rm = (RAWMOUSE *) ((byte *) raw + 16 + 8);
				else rm = &raw->data.mouse;

				// detect buttons (5 buttons with 2 states each)
				for (int j = 0; j < 10; j++)
					if (rm->usButtonFlags & (1 << j)) Key_Event (K_MOUSE1 + (j >> 1), !(j & 1));

				// detect wheel
				if (rm->usButtonFlags & RI_MOUSE_WHEEL)
				{
					if ((signed short) rm->usButtonData > 0)
					{
						Key_Event (K_MWHEELUP, TRUE);
						Key_Event (K_MWHEELUP, FALSE);
					}
					else if ((signed short) rm->usButtonData < 0)
					{
						Key_Event (K_MWHEELDOWN, TRUE);
						Key_Event (K_MWHEELDOWN, FALSE);
					}
				}

				// accumulate movement
				mx += rm->lLastX;
				my += rm->lLastY;
			}
		}

		// clean the buffer and remove messages from the system
		DefRawInputProc (paRawInput, nInput, sizeof (RAWINPUTHEADER));
	}

	// and release the scratch memory used for our buffers
	qScratch->FreeToLowMark (mark);

	// move around
	if (in_mouselooking)
	{
		// move around
		cl.viewangles[YAW] -= m_yaw->value * (float) mx * sensitivity->value * 1.666f;
		cl.viewangles[PITCH] += m_pitch->value * (float) my * sensitivity->value * 1.666f;

		// clamp pitch
		CL_BoundViewAngle (PITCH, -70, 80);
	}
}


void IN_Frame (void)
{
	// keep the mouse state good
	IN_SetMouseCapture ();

	// update the cursor clipping rect
	IN_UpdateCursorClip ();

	// and now we can read our mouse
	IN_ReadRawInput ();
}


void IN_Init (void)
{
	// mouse variables
	freelook = Cvar_Get ("freelook", "1", CVAR_ARCHIVE);
	Cmd_AddCommand ("force_centerview", Force_CenterView_f);

	// always revert to the arrow when we deactivate the mouse
	hArrowCursor = LoadCursor (NULL, IDC_ARROW);

	// initially capture the mouse unless they asked not to have it
	if (COM_CheckParm ("-nomouse"))
		IN_Shutdown ();
	else IN_CaptureMouse (TRUE);
}


void IN_Shutdown (void)
{
	// this now just needs to release the mouse
	IN_ReleaseMouse (TRUE);
}


